package gamecomponents;

import gamecomponents.sound.SoundManager;
import input.InputManager;

import java.awt.Point;
import java.awt.event.KeyEvent;


import com.threed.jpct.Matrix;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.World;

/**
 * Represents a player.
 * @author Team Deer
 *
 */
public class Player extends com.threed.jpct.Camera {

	private static final long serialVersionUID = 1L;
	private final World world;
	private final SimpleVector boundingEllipsoidSize;

	/**
	 * Some static Directions.
	 * @author Team Deer
	 *
	 */
	public enum Directions {
		up(new SimpleVector(0, -1, 0)), forward(new SimpleVector(0, 0, -1)), backward(
				new SimpleVector(0, 0, 1)), left(new SimpleVector(1, 0, 0)), right(
				new SimpleVector(-1, 0, 0));
		private SimpleVector vector;

		Directions(final SimpleVector v) {
			this.vector = v;
		}

		public SimpleVector getVector() {
			return this.vector;
		}
	}

	private final float groundLevel;

	private final SimpleVector position = new SimpleVector();
	private final SimpleVector velocity = new SimpleVector();
	private float maxSpeed = 1f;

	private float rotationX;
	private float rotationY;

	private final SimpleVector offset; // Offset without collision

	private static java.util.Random random = new java.util.Random();

	private boolean hasChanged = false;

	// Constructor

	/**
	 * Constructs a new Player.
	 */
	public Player(final World world, final SimpleVector startPosition,
			final float rotationY, final float rotationX,
			final float playerHeight) {

		this.world = world;

		this.groundLevel = playerHeight;

		// Initialize Camera Offset
		this.offset = new SimpleVector();

		this.position.set(startPosition);
		this.setPosition(startPosition);

		final SimpleVector lookAtVector = new SimpleVector(
				Directions.forward.getVector());
		lookAtVector.rotateX(rotationX);
		lookAtVector.rotateY(rotationY);
		this.rotationX = rotationX;
		this.rotationY = rotationY;

		lookAtVector.add(this.getPosition());
		this.lookAt(lookAtVector);

		this.boundingEllipsoidSize = new SimpleVector(1.25f, 1.25f, 1.25f);

		// Add sounds:
		for (int i = 1; i <= 4; i++) {
			SoundManager.getInstance().addSound("run" + i,
					"Content\\Waves\\run" + i + ".wav");
		}

		this.world.setCameraTo(this);
	}

	// Methods
	
	/**
	 * Get max speed.
	 * @return the max speed
	 */
	public float getMaxSpeed() {
		return this.maxSpeed;
	}

	/**
	 * Sets the max speed.
	 * @param maxSpeed the max speed.
	 */
	public void setMaxSpeed(final float maxSpeed) {
		this.maxSpeed = maxSpeed;
	}

	/**
	 * 
	 * @return the player position
	 */
	public SimpleVector getPlayerPosition() {
		return this.position;
	}

	/**
	 * 
	 * @return the player rotation
	 */
	public float getPlayerRotationY() {
		return this.rotationY;
	}

	/**
	 * Updates this player.
	 * @param inputManager the inputmanager
	 * @return true if this has changed and View needs to redraw
	 */
	public boolean update(final InputManager inputManager) {
		this.hasChanged = false;
		final boolean isRunning = checkForMovements(inputManager);

		// No button pressed, slow down
		if (!isRunning) {
			this.velocity.x *= .98f;
			this.velocity.z *= .98f;
			if (this.velocity.x < .01f || this.velocity.z < .01f) {
				this.velocity.x = 0;
				this.velocity.z = 0;
			}
		}

		this.hasChanged = this.velocity.length() > 0;
		// Running
		if (this.hasChanged) {
			this.movePlayer(isRunning);
		}

		this.rotateCamera(inputManager.pollMouseMovement());

		return this.hasChanged;
	}

	/**
	 * Checks for player movements
	 * @param inputManager the inputmanager
	 * @return true if player is running
	 */
	private boolean checkForMovements(final InputManager inputManager) {
		boolean isRunning = false;
		if (inputManager.getKeyboardState().isKeyPressed(KeyEvent.VK_W)) {
			final SimpleVector dVelocity = new SimpleVector(
					Directions.forward.getVector());
			dVelocity.scalarMul(.1f);
			this.velocity.add(dVelocity);
			isRunning = true;
		}
		if (inputManager.getKeyboardState().isKeyPressed(KeyEvent.VK_S)) {
			final SimpleVector dVelocity = new SimpleVector(
					Directions.backward.getVector());
			dVelocity.scalarMul(.1f);
			this.velocity.add(dVelocity);
			isRunning = true;
		}
		if (inputManager.getKeyboardState().isKeyPressed(KeyEvent.VK_A)) {
			final SimpleVector dVelocity = new SimpleVector(
					Directions.left.getVector());
			dVelocity.scalarMul(.1f);
			this.velocity.add(dVelocity);
			isRunning = true;
		}
		if (inputManager.getKeyboardState().isKeyPressed(KeyEvent.VK_D)) {
			final SimpleVector dVelocity = new SimpleVector(
					Directions.right.getVector());
			dVelocity.scalarMul(.1f);
			this.velocity.add(dVelocity);
			isRunning = true;
		}

		if (inputManager.getKeyboardState().isKeyPressed(KeyEvent.VK_SPACE)
				&& this.position.y == this.groundLevel) {
			final SimpleVector dVelocity = new SimpleVector(
					Directions.up.getVector());
			dVelocity.scalarMul(3f);
			// this.velocity.add(dVelocity);
		}
		return isRunning;
	}

	/**
	 * Perform the movement with collision
	 * @param isRunning if player is running
	 */
	private void movePlayer(final boolean isRunning) {

		final SimpleVector tempOffset = new SimpleVector();

		tempOffset.x = .15f * (float) Math
				.cos(.01 * System.nanoTime() / 1000000);
		tempOffset.y = Math
				.abs(.2f * (float) Math.cos(.01 * System.nanoTime() / 1000000));
		if (Math.abs(tempOffset.x) > .145f) {
			SoundManager.getInstance().playSound(
					"run" + (random.nextInt(4) + 1));
		}

		if (this.velocity.length() > this.maxSpeed) {
			this.velocity.normalize();
			this.velocity.scalarMul(this.maxSpeed);
		}

		this.setPosition(this.position);

		final Matrix movementRotationMatrix = new Matrix();
		movementRotationMatrix.rotateY(this.rotationY);

		final SimpleVector pVelocity = new SimpleVector(this.velocity);

		final SimpleVector sVelocity = new SimpleVector();
		if (isRunning) {
			sVelocity.add(tempOffset);
		}
		sVelocity.add(this.offset);

		// Get both the offsets and the main transformation rotated
		pVelocity.rotate(movementRotationMatrix);
		sVelocity.rotate(movementRotationMatrix);

		this.world.checkCameraCollisionEllipsoid(pVelocity,
				this.boundingEllipsoidSize, pVelocity.length(), 3);

		this.position.set(this.getPosition());
		sVelocity.add(this.position);
		this.setPosition(sVelocity);
	}

	/**
	 * Rotates the camera
	 * @param mousePosition the recent mouse movement
	 */
	private void rotateCamera(final Point mousePosition) {
		this.hasChanged = this.hasChanged
				|| (mousePosition.x != 0 && mousePosition.y != 0);

		this.rotationX -= mousePosition.y / 1000f;
		this.rotationY += mousePosition.x / 1000f;

		if (this.rotationX > 1.2f) {
			this.rotationX = 1.2f;
		}
		if (this.rotationX < -1.2f) {
			this.rotationX = -1.2f;
		}

		final Matrix rotationMatrix = new Matrix();
		rotationMatrix.rotateX(this.rotationX);
		rotationMatrix.rotateY(this.rotationY);

		final SimpleVector lookAtVector = new SimpleVector(
				Directions.forward.getVector());
		lookAtVector.rotate(rotationMatrix);

		lookAtVector.add(this.getPosition());
		this.lookAt(lookAtVector);
	}
}